Skip to content

Latest commit

 

History

History
2187 lines (1623 loc) · 84.1 KB

DeveloperGuide.adoc

File metadata and controls

2187 lines (1623 loc) · 84.1 KB

Planno - Developer Guide

1. Welcome to Planno

Planno is a Java application that helps users to conveniently manage contact as well as event details. To achieve this, Planno provides a large number of simple commands for users.

There are many ways to contribute to Planno: coding, testing, improving the build process and tools, or contributing to the documentation. This guide provides information that will not only help you get started as a Planno contributor, but that you’ll find it useful to refer to even if you are already an experienced contributor.

This developer guide aims to give future programmers who hope to modify this application a general description of the architecture and design of the Desktop Application Planno. Planno provides users a platform to store and manage people’s contact information, and events related to people in Planno. This developer guide consists of the following sections:

2. Setting up

2.1. Prerequisites

  1. JDK 1.8.0_60 or later

    ℹ️
    Having any Java 8 version is not enough.
    This app will not work with earlier versions of Java 8.
  2. IntelliJ IDE

    ℹ️
    IntelliJ by default has Gradle and JavaFx plugins installed.
    Do not disable them. If you have disabled them, go to File > Settings > Plugins to re-enable them.

2.2. Setting up the project in your computer

To set up the project in your computer, please follow the following steps:

  1. Fork this repo, and clone the fork to your computer

  2. Open IntelliJ (if you are not in the welcome screen, click File > Close Project to close the existing project dialog first)

  3. Set up the correct JDK version for Gradle

    1. Click Configure > Project Defaults > Project Structure

    2. Click New…​ and find the directory of the JDK

  4. Click Import Project

  5. Locate the build.gradle file and select it. Click OK

  6. Click Open as Project

  7. Click OK to accept the default settings

  8. Open a console and run the command gradlew processResources (Mac/Linux: ./gradlew processResources). It should finish with the BUILD SUCCESSFUL message.

The above steps will help you generate all resources required by the application and tests.

2.3. Verifying the setup

  1. Run the seedu.address.MainApp and try a few commands

  2. Run the tests to ensure they all pass.

2.4. Configurations to do before writing code

2.4.1. Configuring the coding style

This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,

  1. Go to File > Settings…​ (Windows/Linux), or IntelliJ IDEA > Preferences…​ (macOS)

  2. Select Editor > Code Style > Java

  3. Click on the Imports tab to set the order

    • For Class count to use import with '*' and Names count to use static import with '*': Set to 999 to prevent IntelliJ from contracting the import statements

    • For Import Layout: The order is import static all other imports, import java.*, import javax.*, import org.*, import com.*, import all other imports. Add a <blank line> between each import

Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.

2.4.2. Updating documentation to match your fork

After forking the repo, links in the documentation will still point to the se-edu/addressbook-level4 repo. If you plan to develop this as a separate product (i.e. instead of contributing to the se-edu/addressbook-level4) , you should replace the URL in the variable repoURL in DeveloperGuide.adoc and UserGuide.adoc with the URL of your fork.

2.4.3. Setting up CI

Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.

Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).

ℹ️
Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based)

2.4.4. Getting started with coding

When you are ready to start coding,

  1. Get some sense of the overall design by reading the Architecture section.

  2. Take a look at the section Suggested Programming Tasks to Get Started.

3. Design

3.1. Architecture

Architecture

Figure 3.1.1: Architecture Diagram

The Architecture Diagram (Figure 3.1.1) given above explains the high-level design of the App. Given below is a quick overview of each component:

💡
The .pptx files used to create diagrams in this document can be found in the diagrams folder. To update a diagram, you can modify the diagram in the pptx file, select the objects of the diagram, and choose Save as picture.

Main has only one class called MainApp. It is responsible for:

  • (At app launch) Initializing the components in the correct sequence, and connects them up with each other.

  • (At app shut down) Shutting down the components and invokes cleanup method where necessary.

Commons represents a collection of classes used by other components. Among them, the following two classes play important roles at the architecture level:

  • EventsCenter : This class is written using Google’s Event Bus library. Components communicate with each other by posting event in this class (i.e. a form of event-driven nature of design).

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of four major components:

  • UI : Displays the user interface.

  • Logic : Executes the command.

  • Model : Holds the data of the App in-memory.

  • Storage : Reads data from, and writes data to, the hard disk.

Each of the four components above

  • Defines its API in an interface with the same name as the Component.

  • Exposes its functionality using a {Component Name}Manager class.

For example, the Logic component defines it’s API in the Logic.java interface and exposes its functionality using the LogicManager.java class.

Events-Driven nature of the design

The Sequence Diagram (Figure 3.1.2) below shows how the components interact using EventsCenter for the scenario where the user issues the command delete 1.

SDforDeletePerson

Figure 3.1.2: Component interactions for delete 1 command (part 1)

ℹ️
The Model simply raises a AddressBookChangedEvent when the Address Book data are changed, instead of asking the Storage to save the updates to the hard disk.

The diagram below (Figure 3.1.3) shows how the EventsCenter reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time.

SDforDeletePersonEventHandling

Figure 3.1.3: Component interactions for delete 1 command (part 2)

ℹ️
The event is propagated through the EventsCenter to the Storage and UI without Model having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components.
The sections below give you more details of each component.

3.2. Four major components

3.2.1. UI component

UiClassDiagram

Figure 3.2.1: Structure of the UI Component

API : Ui.java

The structure of the UI Component is shown in the Class Diagram (Figure 2.2.1) above. The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, StatusBarFooter, BrowserPanel etc. All these, including the MainWindow, inherit from the abstract UiPart class.

The UI component uses JavaFx UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml

The UI component:

  • Executes user commands using the Logic component.

  • Binds itself to some data in the Model so that the UI can auto-update when data in the Model changes.

  • Responds to events raised from various parts of the App and updates the UI accordingly.

3.2.2. Logic component

The diagrams (Figure 3.2.2.1 to Figure 3.2.2.3) given below show the structure of whole logic component, and structure of commands in details.

LogicClassDiagram

Figure 3.2.2.1: Structure of the Logic Component

LogicCommandClassDiagram

Figure 3.2.2.2: Structure of Commands in the Logic Component. This diagram shows finer details concerning XYZCommand and Command in Figure 3.2.2.1

API : Logic.java

Figure 3.2.2.1 shows the structure of the Logic component. The LogicManager firstly calls the AddressBookParser to parse the user input. This results in a Command object which is executed by the LogicManager. Then, the command execution can affect the Model (e.g. adding a person) and/or raise events. The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui.

Given below is the Sequence Diagram (Figure 3.2.2.3) for interactions within the Logic component for the execute("delete 1") API call.

DeletePersonSdForLogic

Figure 3.2.2.3: Interactions Inside the Logic Component for the delete 1 Command

3.2.3. Model component

The following diagram (Figure 3.2.3) shows the class structure of the Model component.

ModelClassDiagram

Figure 3.2.3: Structure of the Model Component

API : Model.java

Generally, the Model is managed by a Model manager, which

  • stores a UserPref object that represents the user’s preferences.

  • maintains an AddressBook and a EventList.

  • stores 2 unmodifiable list: ObservableList<ReadOnlyPerson> and ObservableList<ReadOnlyEvent>. They are bounded to UI so that the UI can automatically updates when the data in the list change.

  • does not depend on any of the other three components.

In detail, the AddressBook and the EventList are respectively responsible for person and event information.

  • The AddressBook

    • stores people’s information as a person list with no duplicate persons. The information includes one’s personal information and contact details.

    • keeps track of all the tags that had been added to some people in the person list

    • for each person in the list, the person holds a modifiable tag list that contains all the tag this person has.

  • The EventList

    • stores event’s information as an event list.

    • for each event in the list, the event maintains a modifiable list to keep track of who the participants of the events are.

3.2.4. Storage component

StorageClassDiagram

Figure 3.2.4: Structure of the Storage Component

API : Storage.java

The diagram (Figure 3.2.4) above shows the structure of the Storage component. The StorageManager handles the saving and loading of data for both AddressBookStorage and EventStorage. XmlSerializableAddressBook and XmlSerializableEventStorage handle the conversion from Java to Xml format using XmlAdaptedPerson, XmlAdaptedEvent and XmlAdaptedTag.

The Storage component:

  • Saves UserPref objects in json format and reads it back.

  • Saves the Address Book data in xml format and reads it back.

  • Saves event storage data in xml format and reads it back.

3.3. Common classes

Classes used by multiple components are in the seedu.addressbook.commons package.

4. Implementation

This section describes some noteworthy details on how certain features are implemented. For the features described in this section, their design considerations are included where applicable.

4.1. Undo/Redo mechanism

This feature helps the user to undo / redo some commands when they find that they have made some mistakes.

4.1.1. General implementation

The undo/redo mechanism is facilitated by an UndoRedoStack, which resides inside LogicManager. It supports undoing and redoing of commands that modifies the state of the address book (e.g. add, edit). Such commands will inherit from UndoableCommand.

UndoRedoStack only deals with UndoableCommands. Commands that cannot be undone will inherit from Command instead. The following diagram (Figure 3.1.1.1) shows the inheritance diagram for commands:

LogicCommandClassDiagram

Figure 4.1.1.1: Structure of commands

As you can see from the diagram (Figure 4.1.1.1), UndoableCommand adds an extra layer between the abstract Command class and concrete commands that can be undone, such as the DeleteCommand. Note that extra tasks need to be done when executing a command in an undoable way, such as saving the state of Planno before execution. UndoableCommand contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the template pattern.

Commands that are not undoable are implemented this way:

public class ListCommand extends Command {
    @Override
    public CommandResult execute() {
        // ... list logic ...
    }
}

With the extra layer, the commands that are undoable are implemented this way:

public abstract class UndoableCommand extends Command {
    @Override
    public CommandResult execute() {
        executeUndoableCommand();
    }

    protected abstract void undo();

    protected abstract void redo();
}

[source,java]
public class DeleteCommand extends UndoableCommand {
    @Override
    public CommandResult executeUndoableCommand() {
        // ... delete logic ...
    }

    @Override
    protected void undo() {
        // ... undo deleting logic ...
    }

    @Override
    protected void redo() {
        // ... redo deleting logic ...
    }
}

As you can see, different types of UndoableCommands have different implementations of undo/redo, which will be included from section 3.1.2 to 3.1.5. Now we can look at an example of how UndoRedoStack works:

Suppose that the user has just launched the application. The UndoRedoStack will be empty at the beginning.

The user executes a new UndoableCommand, delete 5, to delete the 5th person in the address book. The personToDelete (which is represented as $5) will be saved inside this delete 5 command for subsequent undo command. The delete 5 command will then be pushed to the undoStack (the current state is saved together with the command). This is shown in the image (Figure 4.1.1.2) below.

UndoRedoStartingStackDiagram

Figure 4.1.1.2: undo example part 1

As the user continues to use the program, more commands are added into the undoStack. For example, the user may execute add n/David …​ to add a new person. This person (which is represented as $David) will be saved in this add command for subsequent undo command. This is show in the image (Figure 4.1.1.3) below.

UndoRedoNewCommand1StackDiagram

Figure 4.1.1.3: undo example part 2

ℹ️
If a command fails its execution, it will not be pushed to the UndoRedoStack at all.

The user now decides that adding the person $David was a mistake, and decides to undo that action using undo.

We will pop the most recent command out of the undoStack and push it back to the redoStack. We will execute the undo() method inside that command. This is shown in the image (Figure 4.1.1.4) below.

UndoRedoExecuteUndoStackDiagram

Figure 4.1.1.4: undo example part 3

ℹ️
If the undoStack is empty, then there are no other commands left to be undone, and an Exception will be thrown when popping the undoStack.

The following sequence diagram (Figure 4.1.1.5) shows how the undo operation works:

UndoRedoSequencediagram

Figure 4.1.1.5: undo sequential diagram

The redo does the exact opposite (pops from redoStack, push to undoStack, and execute the redo() method inside that method).

ℹ️
If the redoStack is empty, then there are no other commands left to be redone, and an Exception will be thrown when popping the redoStack.

The user now decides to execute a new command, clear. As before, clear will be pushed into the undoStack. This time the redoStack is no longer empty. It will be purged as it no longer make sense to redo the add n/David command (this is the behavior that most modern desktop applications follow). This is shown in the image (Figure 3.1.1.6) below.

UndoRedoNewCommand2StackDiagram

Figure 4.1.1.6: redo example part 1

Commands that are not undoable are not added into the undoStack. For example, list, which inherits from Command rather than UndoableCommand, will not be added after execution. This is shown in the image (Figure 4.1.1.7) below.

UndoRedoNewCommand3StackDiagram

Figure 4.1.1.7: redo example part 2

The following activity diagram (Figure 4.1.1.8) summarize what happens inside the UndoRedoStack when a user executes a new command.

UndoRedoActivityDiagram

Figure 4.1.1.8: undo/redo activity

Design considerations

Aspect: Implementation of UndoableCommand
Alternative 1 (current choice): Add a new abstract method executeUndoableCommand()
Pros: We will not lose any undone/redone functionality as it is now part of the default behaviour. Classes that deal with Command do not have to know that executeUndoableCommand() exist.
Cons: It might be hard for new developers to understand the template pattern.
Alternative 2: Just override execute()
Pros: This implementation does not involve the template pattern, and is easier for new developers to understand.
Cons: Classes that inherit from UndoableCommand must remember to call super.execute(), or lose the ability to undo/redo.


Aspect: How undo & redo executes
Alternative 1 (current choice): Implement undo/redo for each undoable command separately
Pros: The app will use less memory (e.g. for delete, just save the person being deleted).
Cons: We must ensure that the implementation of each individual command are correct.
Alternative 2: Save the entire address book.
Pros: This is easy to implement.
Cons: This may cause performance issues in terms of memory usage.


Aspect: Type of commands that can be undone/redone
Alternative 1 (current choice): Only include commands that modifies the address book (add, clear, edit)
Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are lost).
Cons: User might think that undo also applies when the list is modified (undoing filtering for example), only to realize that it does not do that, after executing undo.
Alternative 2: Include all commands
Pros: This could be more intuitive for users.
Cons: User have no way of skipping such commands if he or she just want to reset the state of the address book and not the view.
Additional Info: See our discussion here.


Aspect: Data structure to support the undo/redo commands
Alternative 1 (current choice): Use separate stack for undo and redo
Pros: New incoming developers of our project, such as new Computer Science undergraduates are easy to understand
Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both HistoryManager and UndoRedoStack.
Alternative 2: Use HistoryManager for undo/redo
Pros: We do not need to maintain a separate stack, and just reuse what is already in the codebase.
Cons: We should deal with commands that have already been undone: We must remember to skip these commands. This violates Single Responsibility Principle and Separation of Concerns, as HistoryManager now needs to do two different things


The following sections provide the specific implementations of undo and redo for some types of UndoableCommand:

4.1.2. Undo an add command

We save the personToAdd for subsequent undoing when we undo add command.
Before adding this person, we identify all tags which are attached personToAdd, but are not in the tag list. In other words, we will extract tags which attach to personToAdd only, and save then in another list: newTags.

ℹ️
There is A list of tags attaching personToAdd. When we undo, we need to eliminate those tags that attach to this person only.

When we undo it, we firstly remove those tags in newTags, and then delete this person from address book.
When we redo it, we will add this person into address book again.

The AddEventCommand (addE) has similar undo/redo implementation to this.


Design Considerations

Aspect: How to deal with tags
Alternative 1 (current choice): Remove all tags which only exists in this person
Pros: This allows the add command to be undone completely.
Cons: We need to ensure each tag that is supposed to be removed does not exist in any other person.
Alternative 2: Not remove any tag
Pros: This is easy to implement.
Cons: Tag list is not reverted to the state completely before add command executes, which may confuse users.


4.1.3. Undo a delete command

We save the personToDelete for subsequent undoing when we execute a delete command.
When we undo it, we add personToDelete back to the address book at its original position before deletion.
When we redo it, we simply delete this person.

The DeleteEventCommand (deleteE) has similar undo/redo implementation to this.

Design Considerations

Aspect: Where to add the target person when we undo the delete person
Alternative 1 (current choice): Add it to the original position index
Pros: This allows the delete command to be undone completely, and the sequence of persons in address book will not change because of the undo.
Cons: Time complexity will increase, because it requires all persons behind index to switch to right.
Alternative 2: Just add it at the back of address book
Pros: This is easy to implement, and is more efficient in terms of time complexity.
Cons: The sequence of persons in address book will change, which may confuse users.


4.1.4. Undo an edit command (portrait command is similar)

We save both the personToEdit and the editedPerson for subsequent undoing when we execute an edit / portrait command.
Similar to add command, we need to save the tags which only attach to editedPerson in a list newTags.
When we undo it, we will firstly remove tags in newTags, and then modify editedPerson to be personToEdit.
When we redo it, we will simply modify personToEdit to be editedPerson.

The EditEventCommand (editE) has similar undo/redo implementation to this.


4.1.5. Undo a join / disjoin command

We save both targetPerson and targetEvent for subsequent undoing when we execute a join / disjoin command.
When we undo it, we will disjoin/ join this person and this event, respectively.
When we redo it, we will join/ disjoin back this person and this event, respectively.


4.1.6. Undo a clear command

We need to save the current state of address book and event list for subsequent undoing when we execute a clear command.
When we undo it, we will restore the address book and event list to the state before the clear command executed. When we redo it, we just clear everything again.


4.2. Find mechanism

The Find mechanism is facilitated by NameContainsKeyWordPredicate class, which resides in model.person package. This command allows users to find a list of persons by tag and name keywords. A person that has at least one of the keywords will be selected. Such command will inherit from Command.

The find command accepts two types of keywords:

  • tag: it is identified by a prefix t/.
    e.g. find t/friends means to find any person that have a tag called friends

  • name: anything that does not begin with tag is identified as name keyword.
    e.g. find friends means to find any person whose name contains the keyword friends

    ℹ️
    if a tag name is not preceded with a prefix t/, then the tag name will be identified as a person name. As a result, the people with their name containing the tag name will be displayed.

The mechanism for NameContainsKeywordPredicate to select people is implemented this way:

@Override
public boolean test(ReadOnlyPerson person) {
	boolean isSelected = keywords.stream().anyMatchkeyword –>
 			StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
	// check whether this person’s name contains any of the name key word
	If (isSelected == true) {
		return isSelected;
	}
	for (String keyword : keywords) {
		If (keyword.length() >= 2 && keyword.substring(0, 2).equals(“t/”) {
			String tagName = “[” + keyword.substring(2) + “]”;
			for(Tag tag : person.getTags()) {
				if (tag.toString().equals(tagName)) {
 					isSelected = true;
 		}}}
	return isSelected;
}

Below is a sequence diagram (Figure 4.2) for executing a find command: find t/friends. It will find persons that have the tag friends.

findSequenceDiagram

Figure 4.2: sequence diagram for find command

The command will be sent to LogicManager, and LogicManager will call AddressBookParser to parse the command. Subsequently, FindCommandParser will parse the argument t/friends and create a new findCommand with predicate t/friends. Then LogicManager will execute findCommand to update the filtered person list with predicate friends.

This update will notify GUI to update the filtered person list, so that the persons with the tag friends is displayed.

Design considerations


Aspect: Implementation of find command
Alternative 1 (current choice): Select people that have any of the keywords
Pros: We only need to ensure that at least one keyword exists for every person.
Cons: It is difficult to know by which keyword a person is selected.
Alternative 2: Select people that has any of the keywords, and highlight selected keywords in each person’s person card
Pros: Shows clearly what keywords each selected person contains.
Cons: We need to go through everything of a person, in order to highlight EVERY keyword the person has.


Aspect: Types of keywords that can be found
Alternative 1 (current choice): Only person name and tag can be used in the find command
Pros: We only need to check a person’s name and tag list to find any matches.
Cons: User will not be able to find a person by other information such as phone or email.
Alternative 2: we allow every information of a person (i.e. phone, email, address) to be used as keywords in the find command.
Pros: A person can be found in many ways.
Cons: Implementation is complicated.

4.3. Sort mechanism

The sort command is facilitated by the LogicManager class. It supports sorting contact list and makes it easier for users to find contacts according to alphabetical order of their names.

The following sequence diagram (Figure 4.3) shows how the sort command works:

SortSequenceDiagram

Figure 4.3: sequence diagram for sort command

When user enters a sort command, it will be received by LogicManager.Then, LogicManager calls AddressBookParser to parse the command. And AddressBookParser will create SortCommand command object and returns it.When LogicManager receives the command object, it will execute it.

The SortCommand object calls sortPersons() in the `Model`as follow:

public class SortCommand extends Command {
    public static final String COMMAND_WORD = "sort";
    public static final String MESSAGE_SUCCESS = "Sorted all persons";
    @Override
    public CommandResult execute() {
        model.sortPersons();
        return new CommandResult(MESSAGE_SUCCESS);
    }
}

And This is how Model update the person list using sortPersons():

    public synchronized void sortPersons() {
        addressBook.sortPersons();
        updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
    }

In the person list, we will sort contacts according to alphabetical order of their names:

    public void sort() {
        Collections.sort(internalList, new Comparator<Person>() {
            public int compare (Person p1, Person p2) {
                return p1.getName().toString().compareToIgnoreCase(p2.getName().toString()); } });
    }

After sorting the person list, SortCommand will create CommandResult object and return it. And LogicManager receives CommandResult and shows related message through UI.

Design considerations

Aspect: How sort command affects the data in the person list
Alternative 1 (current choice): Sort the copy of contact list and return it
Pros: It is more defensive and keeps the data unchanged.
Cons: Users have to sort the list every time when they open the application.
Alternative 2: Sort the actual contact list and return it
Pros: Users do not need to type the command every time.
Cons: Sort command is not undoable which means that the order of list cannot change anymore after you call it. It is also less defensive because you can frequently change the original data.


Aspect: How sort command executes for the person list+ Alternative 1 (current choice): Sort the contact list according to alphabetical order (ascending order)
Pros: It is easy to implement.
Cons: Users do not have other choices to sort the person list.
Alternative 2: Sort the contact list according to different order (ascending order or descending order)
Pros: Users will have more choices.
Cons: It will be more complex for developers to maintain and test.


Aspect: What can be sorted in the person list
Alternative 1 (current choice): Sort contacts according to their names
Pros: It is useful and necessary for general users.
Cons: Users do not have other choices to sort the person list.
Alternative 2: Sort contacts according to their addresses/emails/phone numbers
Pros: It provides more choices for users.
Cons: Because address/email/phone number is unique, you can usually find them without sorting the list.


4.4. Event Model

To support the user managing different events, we create an event model. The Figure 4.4.1 shows what attributes include in the event

DG event class

Figure 4.4.1 event class

To store a brunches of events, we use the EventList class. Here we apply n-Tier architectural style. Inside the EventList, there is a UniqueEventList, where all Events in it should be different. This is shown in Figure 4.4.2

DG nTier event

Figure 4.4.2 n-Tier style of EventList

Note that the EventList is separate from AddressBook. This can reduce coupling between Person and Event, and satisfies "Separation of Concerns Principle" .

Inside the EventList, we add the add, delete, edit commands to manage the events.

Design Considerations

Aspect: How to implement EventList+ Alternative 1 (current choice): Make it as a separate class from AddressBook
Pros: This reduces coupling between Person and Event, and make it easier to manage Event.
Cons: It requires a lot of work to implement the whole system.
Alternative 2: Put it as an attribute inside AddressBook
Pros: This is easy to implement.
Cons: This violates SRP, where AddressBook need to manage both Person and Event.


4.5. AddEvent mechanism

The add event command is facilitated by LogicManager class. It allows user to add a new event to the event list.

The following sequence diagram (Figure 4.5) shows how add event command works:

AddEventCommandSequenceDiagram

Figure 4.5: AddEvent command sequence diagram

The user enters an addEvent command, and the command is received by LogicManager. Then LogicManager calls AddressBookParser to parse the command. AddressBookParser will first check the format of the parameters. If the format is valid, it constructs a new AddEventCommand object. Logic Manager then executes AddEventCommand to add the event into Model. Then Logic Manager will return the command result generated by AddEventCommand to UI.


4.6. ListEvent mechanism

The list event command is facilitated by the LogicManager class. It supports listing all the events for users to process.

The following sequence diagram (Figure 4.6) shows how the list event command works:

ListEventSequenceDiagram

Figure 4.6: sequence diagram for list command

When user enters a listE command, it will be received by LogicManager. The LogicManager calls AddressBookParser to parse the command. Then, AddressBookParser creates the ListEventCommand object and returns it. When LogicManager receives ListEventCommand, it will execute the command.

When ListEventCommand is executed, it will call updateFilteredEventList() method in the Model as follows:

public class ListEventCommand extends Command {
    public static final String COMMAND_WORD = "listE";
    public static final String MESSAGE_SUCCESS = "Listed all events";
    @Override
    public CommandResult execute() {
        model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS);
        return new CommandResult(MESSAGE_SUCCESS);
    }
}

After Model updates filtered event list which will be shown in the GUI, ListEventCommand will create CommandResult object and returns it. And LogicManager receives CommandResult and shows related message through UI.


4.7. DeleteEvent mechanism

The delete event command is facilitated by the LogicManager. It supports undo and redo as it inherits from UndoableCommand. Other delete commands are implemented similarly.

The following sequence diagram (Figure 4.7) below shows how the delete event operation works:

DeleteEventSequenceDiagram

Figure 4.7: sequence diagram for deleteE command

The user enters a deleteE command which is received by LogicManger. LogicManager calls AddressBookParser to parse the user command. AddressBookParser creates an DeleteEventCommand object and returns it. LogicManager receives the Command object and executes it. The DeleteEventCommand calls deleteEvent() in the Model. Model will proceed to delete the event. The DeleteEventCommand then creates a CommandResult object and returns it to LogicManager. LogicManager receives the result and displays it through the UI.

Design Considerations

Aspect: Implementation of DeleteEventCommand
Alternative 1 (current choice): Create a new command to delete events
Pros: It is responsible only for deleting events, and not people. Easier to implement.
Cons: Users may accidentally type in "delete" for deleting a person instead of "deleteE" for deleting an event.
Alternative 2: Modify DeleteCommand to handle deletion for both persons and events
Pros: Users would be less likely to type the wrong command.
Cons: Any changes to the deletion process of either a person or event may affect the other. Harder to implement.


Aspect: How deleteE command executes
Alternative 1 (current choice): Deletes the event at the specified index
Pros: Easy to imlement.
Cons: User has to list/sort event list first.
Alternative 2: Delete the event with the specified name
Pros: Users can delete directly without having to get the index.
Cons: Users would have to type more if the event name is long. It will be more complex for developers to maintain and test.

4.8. EditEvent mechanism

The EditEvent Command is facilitated by the LogicManager. It supports undo and redo as it inherits from UndoableCommand.

The Logic manager will firstly call the parser to parse the user input. The parser will generate a descriptor which contains information of edited attributes.
Instead of accessing into the Event inside Event List and modify its attributes, EditEvent will create an event with certain attributes modified by descriptor. Then it simply replaces the original event in the Event List.

The following code shows how it works:

@Override
protected CommandResult executeUndoableCommand() {
    // logic of identifying target event

    Event editedEvent = descriptor.build();
    model.update(targetEvent, editedEvent);
}

Note that the update(targetEvent, editedEvent) method only replace targetEvent by editedEvent.


4.9. ShowParticipants mechanism

The show participants mechanism is facilitated by PersonJoinsEventsPredicate, which resides in model.person package. This command will help users to find participants of an event. For achieving this function, PersonJoinsEventsPredicate will filter the person list. This command inherits from Command.

The following sequence diagram (Figure 4.9) for executing a show participants command: showP 3. It will show all the participants of the third event in the current list.:

ShowParticipantsSequenceDiagram

Figure 4.9: sequence diagram for showP INDEX command

When user enters a showP 3 command, it will be received by LogicManager. And LogicManager calls AddressBookParser to parse the command. Then AddressBookParser will create ShowParticipantsCommandParser to parse 3.

After ShowParticipantsCommandParser parses the index 3, it will create ShowParticipantsCommand object with this index as follows:

    public ShowParticipantsCommand parse(String args) throws ParseException {
        try {
            Index index = ParserUtil.parseIndex(args);
            return new ShowParticipantsCommand(index);
        } catch (IllegalValueException ive) {
            throw new ParseException(
                    String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShowParticipantsCommand.MESSAGE_USAGE));
        }
    }

Then AddressBookParser receives the command object and returns it to LogicManager. When LogicManager receives ShowParticipantsCommand object, it will execute it.

ShowParticipantsCommand will create the PersonJoinsEventsPredicate which is p in the diagram using the index given and use this predicate to update the person list as follows:

    @Override
    public CommandResult execute() throws CommandException {
        List<ReadOnlyEvent> lastShownList = model.getFilteredEventList();
        if (targetIndex.getZeroBased() >= lastShownList.size()) {
            throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
        }
        eventToShow = lastShownList.get(targetIndex.getZeroBased());
        String name = eventToShow.getEventName().fullEventName;
        PersonJoinsEventsPredicate predicate = new PersonJoinsEventsPredicate(name);
        model.updateFilteredPersonList(predicate);
        return new CommandResult(String.format(MESSAGE_SHOW_PARTICIPANTS_SUCCESS, eventToShow.getEventName()));
    }

And that is how PersonJoinsEventsPredicate filters the person list:

    @Override
    public boolean test(ReadOnlyPerson person) {
        Boolean isSelected = false;
        if (!person.getParticipation().isEmpty()) {
            for (ReadOnlyEvent event: person.getParticipation()) {
                if (!isSelected) {
                    isSelected = keywords.equals(event.getEventName().fullEventName);
                }
            }
        }
        return isSelected;
    }

After Model updates the person list which will be shown in the GUI, the ShowParticipantsCommand object will create CommandResult object and returns it. And LogicManager receives CommandResult and shows related message through UI.

Design Considerations

Aspect: How show participants command executes
Alternative 1 (current choice): Show participants of the event at the specified index
Pros: It is easy to implement.
Cons: Sometimes users have to list all the events so that they can get all the indexes.
Alternative 2: Show participants of the event with the specified name
Pros: Users can use the command without listing events.
Cons: Users need more time to type if the event name is too long.

4.10. ShowAllJoinedEvent mechanism

The selectE command is facilitated by the LogicManager. This command inherits from Command.

The following sequence diagram (Figure 4.8) shows how the show all joined events operation works:

ShowAllJoinedEventsSequenceDiagram

Figure 4.10: sequence diagram for selectE command

As seen in the sequence diagram (Figure 4.10) above, when the user enters a selectE command, the input is parsed by the SelectJoinedEventsCommandParser. A SelectJoinedEventsCommand is then created from the parsed user input. When the LogicManager executes the SelectJoinedEventsCommand, the latter calls getFilteredPersonList() to get the current person list. The SelectJoinedEventsCommand will then check the entered index values and get the names of the events the person/s have participated. This is shown in the code snippet below:

public class SelectJoinedEventsCommand extends Command {
    @Override
    public CommandResult execute() throws CommandException {
        for (Index targetIndex: indexList) {
                    if (targetIndex.getZeroBased() >= lastShownList.size()) {
                        throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
                    }
                    personNames.append(lastShownList.get(targetIndex.getZeroBased()).getName()).append(", ");
                    for (ReadOnlyEvent event: lastShownList.get(targetIndex.getZeroBased()).getParticipation()) {
                    eventNames.append(event.getEventName()).append("[-]");
                    }
                }
    }
}

As seen in the code snippet above, the SelectJoinedEventsCommand will throw an exception if the entered index values are invalid and will combine the participated event names into a String named "eventNames". The SelectJoinedEventsCommand will then separate the event names using the combination of symbols, "[-]", so they can be used by a predicate to find the participated events. This is shown in the code snippet below:

public class SelectJoinedEventsCommand extends Command {
	@Override
	public CommandResult execute() throws CommandException {
		String[] eventNameKeywords = (eventNames.toString().trim()).split("\\[-]+");
		EventContainsKeywordPredicate predicate = new EventContainsKeywordPredicate(Arrays.asList(eventNameKeywords));
		// Update the UI and return result.
	}
}

As shown in the code snippet above, the event names will be separated and stored in a String[] named "eventNameKeywords". A EventContainsKeywordPredicate will be created and will use the "eventNameKeywords" to find the events that are participated by the entered person/s. The UI will then be updated to display the events found.

Design Considerations

Aspect: How selectE command executes
Alternative 1 (current choice): Shows events joined by person/s at the selected indexes
Pros: User does not have to type out names.
Cons: User has to list/sort person list first.
Alternative 2: Show events joined by person/s using user entered name
Pros: User can directly enter names to search for events where a person with the entered name has joined.
Cons: Users would have to type more if the name is long. User may mistype the name.

4.11. Person-Event interaction

Events and persons can be connected (i.e.joined) if a person participates in an event. The connected relationship can also be disjoined. We will explain the implementation of the relationship in terms of Model and Storage.

Model level

For model, we simply use a "referencing model" to show the relationship between person and event, as shown in the following diagram.

DG model join

Figure 4.11.1: interaction model

There is a participant list in the Event model, which stores all the persons who are involved in this event. Similarly, the participation list in Person model stores all the event in which this person joins. Hence, we need to maintain both lists when operating join and disjoin commands.

Storage level

In storage, we cannot use the referencing model which is used in the Model component. This is because that it is easy to have an Infinity loop of reference. As shown in the diagram (Figure 4.11.2) below, an event A references a person X, and X references another event B, and B continues to reference another person Y…​.As a result, the referencing list will be incredibly long:

DG infinity loop

Figure 4.11.2: infinity loop

To solve the infinity loop reference, we create two storage entities: XmlAdaptedEventNoParticipant and XmlAdaptedPersonNoParticipation for referencing purposes. Both of them do not have participant or participation information. Hence, we can avoid the infinity referencing problem.
The updated storage is shown in the diagram (Figure 4.11.3) below:

DG join storage

Figure 4.11.3: interaction storage

Design Considerations

Aspect: How to implement person-event interaction
Alternative 1 (current choice): Set participationList in Person, and participantList in Event. They reference each other
Pros: This is easy to implement, and fulfill what we need.
Cons: We need to maintain both lists when operating join and disjoin.
Alternative 2: Use association class
Pros: This allows us to save every participation entry.
Cons: We need to implement extra storage for association class, and it is costly in terms of time to operate show person and select event.

Disconnecting a person to an event: disjoin
This command will call ModelManager 's quitEvent() method to disconnect the person and the event. The following code segment from ModelManager shows how quitEvent() works:

public void disjoin(Person personToRemove, Event eventToRemove) {
    eventList.removeParticipant(personToRemove); //Maintain participantList in Event
    personList.removeParticipant(eventToRemove); //Maintain participationList in Person

    // Save changes to the storage.
}

PersonList and EventList will then locate the person/event-to-remove in its list, and perform deletion.

Connecting a person to an event: join
Similar to disjoin command, join event follows the same mechanism. This command will call ModelManager ’s joinEvent() method to connect the person and the event, as shown in the following code segment. Subsequently, PersonList and EventList will then locate the person/event-to-add in its list, and perform addition.

public void join(Person personToAdd, Event eventToAdd) {
    eventList.addParticipant(personToAdd); //Maintain participantList in Event
    personList.addParticipant(eventToAdd); //Maintain participationList in Person

    // Save changes to the storage.
}

4.12. Portrait Command mechanism

The portrait command is facilitated by LogicManager and is extended from UndoableCommand.
The following diagram (Figure 4.12.1) shows the structure of PortraitPath class:

DG Portrait class

Figure 4.12.1 PortraitPath class structure

As shown in above diagram, we only store a string value, which is the file path in this class. We load the image file in the UI component.

The portrait can only be changed by this command. In other words, Add command can only create a person without a portrait.

However, users may type a wrong path. As we only load it in the UI component, we will only know if any errors occur when it reaches the UI component, which makes handling exceptions harder as they are usually handled in the Logic component. Therefore, we apply defensive coding here. When the app knows that it cannot load the file by this path, a default picture will be loaded. The following code shows it:

if (filePath.isEmpty() || !new File(filePath).exists()) {
    url = PortraitPath.DEFAULT_PORTRAIT_PATH;
} else {
    url = PortraitPath.FILE_PREFIX + filePath;
}

Image portrait = new Image(url);
Design Considerations

Aspect: How to store the portrait.
Alternative 1 (current choice): Only store the path , and only load the image in UI.
Pros: This is easy to implement, and this uses less memory.
Cons: User cannot move the image file in the computer, otherwise the user needs to change the path.
Alternative 2: Store the image file.
Pros: The app does not need to load the image every time.
Cons: It is hard to store images into .xml file, and consumes a lot of memory.


4.13. Toggle mechanism

The toggle mechanism is an event-driven mechanism. The following diagram (Figure 4.13.1) below shows the overview of the high-level interactions between components for the toggle mechanism:

DG ToggleComponents

Figure 4.13.1 Toggle mechanism component interactions

The following sequence diagram (Figure 4.13.2) below shows how the first half of the toggle mechanism works up till the posting of the event:

DG Toggle1

Figure 4.13.2 Toggle mechanism sequence 1

As seen in the sequence diagram (Figure 4.13.2) above, when the user enters a toggle command, a ToggleCommand is created. When the LogicManager executes the ToggleCommand, the EventsCenter will post a TogglePanelEvent to the EventBus which is shown in the code snippet below:

public class ToggleCommand extends Command {
    @Override
    public CommandResult execute() throws CommandException {
        EventsCenter.getInstance().post(new TogglePanelEvent());
        // ... Return CommandResult ...
    }
}

The following sequence diagram (Figure 4.13.3) below shows the second half of the toggle mechanism starting with the handling of the posted event:

DG Toggle2

Figure 4.13.3 Toggle mechanism sequence 2

As seen in the sequence diagram (Figure 4.13.3) above, the event is handled by the TogglePanel. The TogglePanel has a variable called browserIsFront which keeps track of whether the browser is currently displayed. TriggerToggle uses browserIsFront to toggle the correct panel to the front and updates browserToFront accordingly. This is shown in the code snippet below:

public class TogglePanel extends UiPart<Region> {
    @Subscribe
    private void handleTogglePanelEvent(TogglePanelEvent event) {
        logger.info(LogsCenter.getEventHandlingLogMessage(event));
        triggerToggle();
    }
    private void triggerToggle() {
        if (browserIsFront) {
            browserToBack();
        } else {
            browserToFront();
        }
    }
    private void browserToFront() {
        browserPlaceHolder.setVisible(true);
        browserPlaceHolder.toFront();
        toggleSplitPane.setVisible(false);
        toggleSplitPane.toBack();
        browserIsFront = true;
    }
    private void browserToBack() {
        browserPlaceHolder.setVisible(false);
        browserPlaceHolder.toBack();
        toggleSplitPane.setVisible(true);
        toggleSplitPane.toFront();
        browserIsFront = false;
    }
}

As seen from the above code snippet, when browserIsFront is true, the place holder where the browser is at, will be sent to the back and made to be invisible. While the toggleSplitPane, which holds the information board and events list, is brought to the front and made visible. BrowserIsFront is then set to be false. When browserIsFront is false, the opposite happens. The place holder with the browser is brought to the front and made visible, while the toggleSplitPane is sent to the back and made to be invisible.

One thing to take note of with the select command, is that the browser will be brought to the front and displayed regardless of the current status of browserIsFront. This is shown in the code snippet below:

public class BrowserPanel extends UiPart<Region> {
    @Subscribe
    private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) {
        logger.info(LogsCenter.getEventHandlingLogMessage(event));
        loadPersonPage(event.getNewSelection().person);
        raise(new ToggleSelectEvent());
    }
}
public class TogglePanel extends UiPart<Region> {
    @Subscribe
    private void handleToggleSelectEvent(ToggleSelectEvent event) {
        logger.info(LogsCenter.getEventHandlingLogMessage(event));
        browserToFront();
    }
}

As seen from the above code snippet, the BrowserPanel handles the event posted whenever the select command is executed. It does so by raising a ToggleSelectEvent, which the TogglePanel handles by calling browserToFront method which will set the browser to be at the front.

Design Considerations

Aspect: How to implement the toggle mechanism.
Alternative 1 (current choice): Use a toggle panel to hold the containers of the browser, information board and events list.
Pros: Easy to implement. Visibility depends on the containers, any modification will not touch the code of the actual panels much if at all.
Cons: Will need to adjust the FXML file when changes are made to what is toggleable.
Alternative 2: Have each toggleable panel know how to toggle.
Pros: No need change the FXML file when changes are made to what is toggleable.
Cons: Will have to make large changes to the actual panels themselves.

4.14. FindEvent mechanism

The find event mechanism is facilitated by EventNameContainsKeyWordPredicate class, which resides in model.event package. This command supports users of finding a list of events by their names. Any event that has either of the entered keyword will be filtered into the list. This command inherits from Command.

Below is a sequence diagram (Figure 4.14) for executing a find event command: findE first. It will find events which contain keyword first in their names.

FindEventSequenceDiagram

Figure 4.14: sequence diagram for find event command

When users enters a findE first, LogicManager will receive it and call AddressBookParser to parse command. In the AddressBookParser, it will create FindEventParser to parse first.

Then FindEventCommandParser will parse argument first and create a new FindEventCommand object with EventNameContainsKeywordPredicate which is p in the diagram:

    public FindEventCommand parse(String args) throws ParseException {
        String trimmedArgs = args.trim();
        if (trimmedArgs.isEmpty()) {
            throw new ParseException(
                    String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindEventCommand.MESSAGE_USAGE));
        }
        String[] nameKeywords = trimmedArgs.split("\\s+");
        return new FindEventCommand(new EventNameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
    }

FindEventCommand will use the predicate to update the event list by calling updateFilteredEventList(p) method in the Model.

And This is how EventNameContainsPredicate filters the event list:

    @Override
    public boolean test(ReadOnlyEvent event) {
        return keywords.stream()
                .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(event.getEventName().fullEventName, keyword));
    }

After Model updates the event list which will be shown in the GUI, the FindEventCommand object will create CommandResult object and returns it. And LogicManager receives CommandResult and shows related message through UI.

Design considerations

Aspect: Implementation of find event command
Alternative 1 (current choice): Select events that have any of the keywords in their names
Pros: It is easier for users to find events without knowing their full names.
Cons: Users may get more unwanted results in the event list.
Alternative 2: Select events that have all the keywords in their names
Pros: Users can find the event they want accurately.
Cons: Sometimes it is hard for users to remember the full name of an event.

4.15. Sort Event mechanism

The sort event command is facilitated by the LogicManager class. It supports sorting the event list according to their dates so that users can decide to process which event first.

The following sequence diagram (Figure 4.15) shows how the sort event command works:

SortEventSequenceDiagram

Figure 4.15: sequence diagram for sort event command

When user enters a sort event command, it will be received by LogicManager.Then, LogicManager calls AddressBookParser to parse the command. And AddressBookParser will create SortEventCommand command object and returns it.When LogicManager receives the command object, it will execute it.

The SortEventCommand object calls sortEvents() in the `Model`as follow:

public class SortEventCommand extends Command {
    public static final String COMMAND_WORD = "sortE";
    public static final String MESSAGE_SUCCESS = "Sorted all events";
    @Override
    public CommandResult execute() {
        model.sortEvents();
        return new CommandResult(MESSAGE_SUCCESS);
    }
}

And This is how Model update the event list using sortEvents() :

    public synchronized void sortEvents() {
        eventList.sortEvents();
        updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS);
    }

In the event list, we will sort events according to their dates;

    public void sort() {
        Collections.sort(internalList, new Comparator<Event>() {
            public int compare (Event p1, Event p2) {
                return p1.getEventTime().orderForSort().compareTo(p2.getEventTime().orderForSort()); } });
    }

After sorting event list, SortEventCommand will create CommandResult object and return it. And LogicManager receives CommandResult and shows related message through UI.

Design considerations

Aspect: How sort event command affects the data in the event list
Alternative 1 (current choice): Sort the copy of event list and return it
Pros: It is more defensive and keeps the data unchanged.
Cons: Users have to sort the list every time when they open the application.
Alternative 2: Sort the actual event list and return it
Pros: Users do not need to type the command every time.
Cons: Sort event command is not undoable which means that the order of list cannot change anymore after you call it. It is also less defensive because you can frequently change the original data.


Aspect: How sort event command executes for the event list+ Alternative 1 (current choice): Sort the event list according to ascending order of their dates
Pros: It is easy to implement.
Cons: Users do not have other choices to sort the event list.
Alternative 2: Sort the event list according to different order (ascending order or descending order)
Pros: Users will have more choices.
Cons: It will be more complex for developers to maintain and test.

4.16. Logging

We are using java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

  • The logging level can be controlled using the logLevel setting in the configuration file (See Configuration)

  • The Logger for a class can be obtained using LogsCenter.getLogger(Class) which will log messages according to the specified logging level

  • Currently log messages are output through: Console and to a .log file.

Logging Levels

  • SEVERE : Critical problem detected which may possibly cause the termination of the application

  • WARNING : Can continue, but with caution

  • INFO : Information showing the noteworthy actions by the App

  • FINE : Details that are not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size

4.17. Configuration

Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: config.json).

5. Documentation

We use asciidoc for writing documentation.

ℹ️
We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting.

5.1. Editing documentation

See UsingGradle.adoc to learn how to render .adoc files locally to preview the end result of your edits. Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc files in real-time.

5.2. Publishing documentation

See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.

5.3. Converting documentation to PDF format

We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.

Here are the steps to convert the project documentation files to PDF format.

  1. Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the docs/ directory to HTML format.

  2. Go to your generated HTML files in the build/docs folder, right click on them and select Open withGoogle Chrome.

  3. Within Chrome, click on the Print option in Chrome’s menu.

  4. Set the destination to Save as PDF, then click Save to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot (Figure 5.3) below.

chrome save as pdf

Figure 5.3: Saving documentation as PDF files in Chrome

6. Testing

6.1. Running tests

There are three ways to run tests.

💡
The most reliable way to run tests is Method 3. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies.

Method 1: Using IntelliJ JUnit test runner

  • To run all tests, right-click on the src/test/java folder and choose Run 'All Tests'

  • To run a subset of tests, right-click on a test package, test class, or a test and choose Run 'ABC'

Method 2: Using Gradle

  • Open a console and run the command gradlew clean allTests (Mac/Linux: ./gradlew clean allTests)

ℹ️
See UsingGradle.adoc for more info on how to run tests using Gradle.

Method 3: Using Gradle (headless)

Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.

To run tests in headless mode, open a console and run the command gradlew clean headless allTests (Mac/Linux: ./gradlew clean headless allTests)

6.2. Types of tests

We have two types of tests:

  1. GUI Tests - These are tests involving the GUI. They include:

    1. System Tests that test the entire App by simulating user actions on the GUI. They are in the systemtests package.

    2. Unit tests that test the individual components. They are in seedu.address.ui package.

  2. Non-GUI Tests - These are tests not involving the GUI. They include:

    1. Unit tests targeting the lowest level methods/classes.
      e.g. seedu.address.commons.StringUtilTest

    2. Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
      e.g. seedu.address.storage.StorageManagerTest

    3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
      e.g. seedu.address.logic.LogicManagerTest

6.3. Troubleshooting testing

Problem: HelpWindowTest fails with a NullPointerException.

  • Reason: One of its dependencies, UserGuide.html in src/main/resources/docs is missing.

  • Solution: Execute Gradle task processResources.

7. Dev ops

7.1. Build automation

See UsingGradle.adoc to learn how to use Gradle for build automation.

7.2. Continuous integration

We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.

7.3. Making a release

Here are the steps to create a new release:

  1. Update the version number in MainApp.java.

  2. Generate a JAR file using Gradle.

  3. Tag the repo with the version number. e.g. v0.1

  4. Create a new release using GitHub and upload the JAR file you created.

7.4. Managing dependencies

A project often depends on third-party libraries. For example, Planno depends on the Jackson library for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives:
a. Including those libraries in the repo (this bloats the repo size)
b. Requiring developers to download those libraries manually (this creates extra work for developers)

Appendix A: User stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *

Priority As a …​ I want to …​ So that I can…​

* * *

user

add a person to an event

keep track of who is involved

* * *

user

delete a person from an event

remove a person who is no longer participating

* * *

user

list all events a person is involved in

easily check which events a person is a participant of

* * *

user

list every person involved in an event

easily check who is participating

* * *

new user

see usage instructions

refer to instructions when I forget how to use the App

* * *

user

add a new person

add a person’s contact detail into the app

* * *

user

add a new event

add an event’s date and information into the app

* * *

user

delete a person or event

remove entries that I no longer need

* * *

user

edit a person or event

change some information of the person or event

* * *

user

clear my address book

refresh it quickly

* * *

user with many friends

list all friends with a certain tag

group my friend by tag easily

* * *

user

add a person event with blank information

add him or the event even if I do not know some details (e.g his address)

* * *

user

find a person by name and tag

locate details of persons without having to go through the entire list

* * *

user

get help information

know where is wrong when I get errors

* * *

user

list contacts

view who are my contacts

* * *

user

list events

view what are my events

* * *

secretive user

hide private details

minimise the chance of someone else seeing them by accident

* * *

careless user

undo decisions

revert changes in case of mistake

* *

user

toggle between the events details and browser

use a browser without having to alternate between many applications

* *

frequent user

change the font and background colour

use address book comfortably

* *

frequent user

use non-case sensitive commands

type commands easily

* *

user

sort contacts

view my list easily

* *

user

tag my contacts

remember who they are through tags

* *

secretive user

hide private contact details by default

minimize chance of someone else seeing them by accident

* *

forgetful user

stick some important people on the top

locate them quickly

* *

user

create filter using multiple tags

make a specific search using tags

*

user

add pictures to contacts

remember who they are through pictures

{More to be added}

Appendix B: Use cases

(For all use cases below, the System is the Planno and the Actor is the user, unless specified otherwise)

Use case: Add a person

MSS

  1. User enters values for a new person.

  2. Planno adds the new person into the database.

    Use case ends.

Extensions

  • 2a. There is already this person.

    • Planno shows a person already exists message.

      Use case ends.

  • 2b. User entered invalid values.

    • Planno shows an invalid values message.

      Use case resumes at step 1.

  • 2b. The list is empty.

    Use case ends.

Use case: Add an event

MSS

  1. User enters values for a new event.

  2. Planno adds the new event into the database.

    Use case ends.

Extensions

  • 2a. There is already this event.

    • Planno shows an event already exists message.

      Use case ends.

  • 2b. User entered invalid values.

    • Planno shows an invalid values message.

      Use case resumes at step 1.

  • 2b. The list is empty.

    Use case ends.

Use case: Delete person

MSS

  1. User requests to list persons.

  2. Planno shows a list of persons.

  3. User requests to delete a specific person in the list.

  4. Planno deletes the person.

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • Planno shows an error message.

      Use case resumes at step 2.

  • 4a. The person is a participant of an event.

    • Planno shows a person is participating in an event message.

      Use case resumes at step 2.

Use case: Delete event

MSS

  1. User requests to list events.

  2. Planno shows a list of events.

  3. User requests to delete a specific event in the list.

  4. Planno deletes the event.

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • Planno shows an error message.

      Use case resumes at step 2.

  • 4a. The event has a participant/s.

    • Planno shows an event has participants message.

      Use case resumes at step 2.

Use case: Update person

MSS

  1. User finds target person by his name.

  2. Planno shows the result of find command.

  3. User types new information for the target person.

  4. Planno updates the information and displays successful message.

    Use case ends.

Extensions

  • 2a. The target person cannot be found.

    Use case ends.

  • 3a. User types invalid information.

    • Planno shows an error message.

      Use case resumes at step 2.

  • 3b. New information is the same as original.

    • Planno shows an unnecessary update message.

      Use case ends.

Use case: Update event

MSS

  1. User finds target event by name.

  2. Planno shows the result of findE command.

  3. User types new information for the target event.

  4. Planno updates the information and displays successful message.

    Use case ends.

Extensions

  • 2a. The target event cannot be found.

    Use case ends.

  • 3a. User types invalid information.

    • Planno shows an error message.

      Use case resumes at step 2.

  • 3b. New information is the same as original.

    • Planno shows an unnecessary update message.

      Use case ends.

Use case: Add a tag to a person

MSS

  1. User enters values for a new person including tag details.

  2. Planno adds the new person into the database.

    Use case ends.

Extensions

  • 2a. There is already this person.

    • Planno shows a person already exists message.

      Use case ends.

  • 2b. The list is empty.

    Use case ends.

Use case: List persons

MSS

  1. User enters "list" command.

  2. Planno displays list of persons.

    Use case ends.

Extensions

  • 1a. There is no person in Planno.

    • Planno shows a successful message.

      Use case ends.

Use case: List events

MSS

  1. User enters "listE" command.

  2. Planno displays list of persons.

    Use case ends.

Extensions

  • 1a. There is no event in EventList.

    • Planno shows a successful message.

      Use case ends.

Use case: Sort persons

MSS

  1. User enters "sort" command.

  2. Planno shows a list of sorted persons.

    Use case ends.

Extensions

  • 2a. The list is empty.

    • Planno shows a successful message.

      Use case ends.

Use case: Find person/s by name

MSS

  1. User enters values for find command.

  2. Planno displays persons with name matching at least one keyword.

    Use case ends.

Extensions

  • 1a. There is no person with a name matching any keyword.

    • Planno shows a blank person list.

      Use case ends.

Use case: Find a person by tag

MSS

  1. User enters tag names for a person list.

  2. Planno displays the list of persons that contains any of the tag names.

    Use case ends.

Extensions

  • 1a. User does not enter tag names.

    • Planno shows an error message.

      Use case ends.

  • 1b. User does not use correct format.

    • Planno displays an empty list.

      Use case ends.

Use case: Find event/s by name

MSS

  1. User enters values for find command.

  2. Planno displays events with name matching at least one keyword.

    Use case ends.

Extensions

  • 1a. There is no event with a name matching any keyword.

    • Planno shows a blank event list.

      Use case ends.

Use case: Select person

MSS

  1. User enters value for list command.

  2. Planno displays list of persons.

  3. User enters index value for select command.

  4. Planno displays Google search page of the person at the entered index value.

    Use case ends.

Extensions

  • 1a. There is no person in Planno.

    • Planno shows an error message.

      Use case ends.

  • 1b. User enters value for find command.

    • Planno displays persons with name matching at least one keyword.

      Use case resumes at step 3.

  • 3a. User enters invalid index value.

    • Planno displays an invalid index message.

      Use case ends.

Use case: Toggle between displaying browser and information board

MSS

  1. User enters value for toggle command.

  2. Planno switches the display from information board to browser if the former is currently displayed (vice versa).

    Use case ends.

Extensions

  • 1a. User enters a select command.

    • Planno displays the browser.

      Use case ends.

Use case: Join a person to an event

MSS

  1. User enters value for list command.

  2. Planno displays list of persons.

  3. User enters value for list event command.

  4. Planno displays list of events.

  5. User enters index value/s of the person list and event list for join command.

  6. Planno adds the person as a participant of the event.

    Use case ends.

Extensions

  • 1a. There is no person in Planno.

    • Planno shows a blank person list.

      Use case ends.

  • 1b. User enters value for find command.

    • Planno displays persons with name matching at least one keyword.

      Use case resumes at step 3.

  • 5a. User enters invalid index value.

    • Planno displays an invalid index message.

      Use case ends.

  • 5b. Person has already joined the event.

    • Planno displays an already joined message.

      Use case ends.

Use case: Disjoin a person from an event

MSS

  1. User enters value for list command.

  2. Planno displays list of persons.

  3. User enters value for list event command.

  4. Planno displays list of events.

  5. User enters index value/s of the person list and event list for disjoin command.

  6. Planno removes the person as a participant of the event.

    Use case ends.

Extensions

  • 1a. There is no person in Planno.

    • Planno shows a blank person list.

      Use case ends.

  • 1b. User enters value for find command.

    • Planno displays persons with name matching at least one keyword.

      Use case resumes at step 3.

  • 5a. User enters invalid index value.

    • Planno displays an invalid index message.

      Use case ends.

  • 5b. Person is not a participant of the event.

    • Planno displays a person does not participate in this event message.

      Use case ends.

Use case: Show participants of an event

MSS

  1. User requests to list events.

  2. Planno shows a list of events.

  3. User requests to show participants of a specific event in the list.

  4. Planno shows participants of the event.

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • Planno shows an error message.

      Use case resumes at step 2.

Use case: Show all joined events of a person/s

MSS

  1. User enters value for list command.

  2. Planno displays list of persons.

  3. User enters value for list event command.

  4. Planno displays list of events.

  5. User enters index value/s of the person list for selectE command.

  6. Planno displays all participated events of the person/s at the entered index value/s.

    Use case ends.

Extensions

  • 1a. There is no person in Planno.

    • Planno shows a blank person list.

      Use case ends.

  • 1b. User enters value for find command.

    • Planno displays persons with name matching at least one keyword.

      Use case resumes at step 3.

  • 3a. There is no event in Planno.

    • Planno displays a blank event list.

      Use case resumes at step 5.

  • 5a. User enters invalid index value.

    • Planno displays an invalid index message.

      Use case ends.

  • 5b. The selected person has not joined any events.

    • Planno displays a blank event list.

      Use case ends.

Use case: Clear Planno

MSS

  1. User enters "clear" command to delete all entries in Planno.

  2. Planno deletes all data.

    Use case ends.

Use case: Undo a command

MSS

  1. User enters "history" command to view entire history of commands used.

  2. Planno displays entire history of commands used.

  3. User enters "undo" to undo the last undoable command.

  4. Planno undoes the last undoable command and displays successful message.

    Use case ends.

Extensions

  • 3a. No more undoable command commands in the history.

    • Planno displays error message.

      Use case ends.

Use case: Redo a command

MSS

  1. User enters "history" command to view entire history of commands used.

  2. Planno displays entire history of commands used.

  3. User enters "redo" to request to redo the last redoable command.

  4. Planno redoes the last redoable command and displays successful message.

    Use case ends.

Extensions

  • 3a. No more redoable command commands in the history.

    • Planno displays error message.

      Use case ends.

B.1. Use case: Show help window

MSS

  1. User enters value for help command.

  2. Planno displays the help window.

    Use case ends.

Extensions

  • 1a. User presses F1 on the keyboard or clicks on help icon.

    Use case resumes at step 2.

Use case: List command history

MSS

  1. User enters value for history command.

  2. Planno displays a list of commands the User has entered from the most recent to earliest.

    Use case ends.

Extensions

  • 1a. User has not entered any previous commands.

    • Planno displays a no previous command entered message.

      Use case ends.

Use case: Exit the app

MSS

  1. User enters "exit" command for exiting the app.

  2. Planno terminates its work and exits.

    Use case ends.

Extensions

  • 1a. User clicks on File → Exit button.

    Use case resumes at step 2.

Appendix C: Non functional requirements

  1. The application should work on any mainstream OS as long as it has Java 1.8.0_60 or higher installed.

  2. The application should be able to hold up to 1000 persons and events without a noticeable sluggishness in performance for typical usage.

  3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.

  4. The application should be able to give feedback messages within 2 seconds for every command.

  5. The user should be able to use the application without connecting to Internet.

  6. The user should be able to use the application without any programming knowledge background.

  7. The size of this application should not exceed 10GB.

  8. The application should work on both 32-bit and 64-bit environments.

  9. The cost of this application should not exceed 10,000 dollars.

  10. The application should protect users' privacy.

  11. Font size should be suitable for people aged 6 to 70 years old.

  12. The application should not tolerate data loss.

  13. The application should be usable to a novice who has never used such types of application before.

  14. The application should startup within 7 seconds

  15. Background color should be appropriate to not discomfort users' eyes.

{More to be added}

Appendix D: Glossary

API

An Application Programming Interface (API) specifies the interface through which other programs can interact with a software component. It is a contract between the component and its clients.

Blank information

An information field with null value. It may happen when a user adds a person and does not know some information.

Build automation

Build automation is the process of automating the creation of a software build and the associated processes.

CI

Continuous integration(CI) is an extreme application of build automation in which integration, building, and testing happens automatically after each code change.

Gradle

Gradle is an open source build automation system that automates the creation of a software build.

Logging

Logging is the deliberate recording of certain information during a program execution for future reference. It can be useful for troubleshooting problems.

Mainstream OS

Windows, Linux, Unix, OS-X.

Private contact detail

A contact detail that is not meant to be shared with others.

Redoable command

A redoable command is a command which has been undone, and no other commands in between.

Travis

Travis CI is a hosted, distributed continuous integration service used to build and test projects hosted at GitHub.

Undoable command

An undoable command is a command which modifies data in Planno. For example, add, delete are undoable commands, while find and list are not.